/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "AppService.h"
#include "PluginNasTypes.h"
#include "ApplicationServiceDataType.h"
#include "Log.h"
#include "common/EnvVarManager.h"
#include "PluginConstants.h"
#include "JsonTransUtil.h"
#include "SmbContextWrapper.h"
#include "ProtectPluginFactory.h"
#include "ErrorCode.h"

using namespace Module;
using namespace PluginUtils;
using namespace AppProtect;

namespace {
    constexpr auto MODULE = "AppService";
    constexpr auto INTERNAL_ERROR = 200;
    constexpr auto HOMOVALUE = "HOMOGENEOUS";
    constexpr auto HETROVALUE = "HETEROGENEOUS";
    constexpr auto PROTOCOL_CIFS = "CIFS";
    constexpr int NUM_64 = 64;
    constexpr int DEFAULT_PAGE_SIZE = 200;
}


namespace AppServiceExport {
void DiscoverApplications(std::vector<Application>& returnValue, const std::string& appType)
{
    returnValue;
    appType;
    return;
}
bool getAuthTypeAndInitTGT(const Application& application, std::string &cifsAuthType)
{
    if (application.auth.authType == AuthType::type::KERBEROS) {
        cifsAuthType = "krb5";
        NasAuthExtentInfo authExtendInfo;
        if (!Module::JsonHelper::JsonStringToStruct(application.auth.extendInfo, authExtendInfo)) {
            HCP_Log(ERR, MODULE) << "NasAuthExtentInfo is invaild." << HCPENDLOG;
            return false;
        }
        if (PluginUtils::KinitTGTInner(application.auth.authkey, authExtendInfo.password,
            authExtendInfo.keytab, authExtendInfo.krb5Conf, application.id) != MP_SUCCESS) {
            HCP_Log(ERR, MODULE) << "Faild to validate kerberos authentication" << HCPENDLOG;
            return false;
        }
    } else if (application.auth.authType == AuthType::type::APP_PASSWORD) {
        cifsAuthType = "ntlmssp";
    } else {
        HCP_Log(ERR, MODULE) << "Invalid authType: " << application.auth.authType << HCPENDLOG;
        return false;
    }
    return true;
}
void CheckApplication(
    ActionResult& returnValue, const ApplicationEnvironment& appEnv, const Application& application)
{
    std::string subtype = application.subType;
    std::string id = application.id;
    std::string nasShareName = application.name;
    HCP_Log(DEBUG, MODULE) << "Application subType: " << subtype
        <<", nasShareName"<< nasShareName << HCPENDLOG;
    std::string localMountPath = "/mnt/" + application.id;
    std::string tempFileName = localMountPath + "/" + "checkMount_" + id;
    std::string nasMountOptions = NFS_MOUNT_OPTION;
    returnValue.code = INTERNAL_ERROR;
    NasShareExtendInfo extendInfo {};
    if (!Module::JsonHelper::JsonStringToStruct(application.extendInfo, extendInfo)) {
        HCP_Log(ERR, MODULE) << "NasShareExtendInfo is invaild." << HCPENDLOG;
        return;
    }
    std::string remoteNasIp = extendInfo.ip;
    std::vector<std::string> ipList;
    ipList.push_back(remoteNasIp);
    std::string protocol = "nfs";
    std::string version = "";
    std::string authType;
    if (extendInfo.shareMode == SMB_PROTOCOL) {
        std::string cifsAuthType;
        if (!getAuthTypeAndInitTGT(application, cifsAuthType)) {
            return;
        }
        CifsConnectParams connectParam {extendInfo.domainName, application.id, version,
                                         nasShareName, application.auth.authkey,
                                         application.auth.authPwd, cifsAuthType,
                                         extendInfo.encryption, ipList};
        if (CheckSmbConnection(connectParam) != MP_SUCCESS) {
            HCP_Log(ERR, MODULE) << "Failed to connect cifs share " << WIPE_SENSITIVE(nasShareName) << HCPENDLOG;
            return;
        }
    } else if (extendInfo.shareMode == NFS_PROTOCOL) {
        NasMountParams mountParams(
            protocol, version, "/" + nasShareName, tempFileName, nasMountOptions, "", "", ipList);
        if (PluginUtils::CheckNfsAccessibility(mountParams) != MP_SUCCESS) {
            HCP_Log(ERR, MODULE) << "Failed to connect nfs share path " << WIPE_SENSITIVE(nasShareName) << HCPENDLOG;
            return;
        }
    } else {
        HCP_Log(ERR, MODULE) << "invalid share mode " << extendInfo.shareMode << HCPENDLOG;
        return;
    }

    returnValue.code = MP_SUCCESS;
    HCP_Log(DEBUG, MODULE) << "The return result is: " << returnValue.code << HCPENDLOG;
    return;
}

void ListApplicationResource(std::vector<ApplicationResource>& returnValue,
                             const ApplicationEnvironment& appEnv, const Application& application,
                             const ApplicationResource& parentResource)
{
    Json::Value appEnvStr;
    StructToJson(appEnv, appEnvStr);
    HCP_Log(INFO, MODULE) << "ListApplicationResource, parameter:" <<
        WIPE_SENSITIVE(appEnvStr.toStyledString()) << HCPENDLOG;
    Json::Value applicationStr;
    StructToJson(application, applicationStr);
    HCP_Log(INFO, MODULE) << "ListApplicationResource, parameter:" <<
        WIPE_SENSITIVE(applicationStr.toStyledString()) << HCPENDLOG;
    Json::Value parentResourceStr;
    StructToJson(parentResource, parentResourceStr);
    HCP_Log(INFO, MODULE) << "ListApplicationResource, parameter:" <<
        WIPE_SENSITIVE(parentResourceStr.toStyledString()) << HCPENDLOG;

    ApplicationResourceExtendInfo applicationResourceExtendInfo;
    if (!JsonHelper::JsonStringToStruct(parentResource.extendInfo, applicationResourceExtendInfo)) {
        HCP_Log(ERR, MODULE) << "share info is invalid!" << HCPENDLOG;
    }
    // wipe sensitive
    HCP_Log(INFO, MODULE) << "parse share info : " << WIPE_SENSITIVE(applicationResourceExtendInfo.nasAgentReq);
    ListApplicationShareInfo shareInfo;
    if (!JsonHelper::JsonStringToStruct(applicationResourceExtendInfo.nasAgentReq, shareInfo)) {
        HCP_Log(ERR, MODULE) << "share info is invalid!" << HCPENDLOG;
    }

    ListResourceParam listResourceParam;
    TransformParm(listResourceParam, shareInfo, application, appEnv);
    PrintListResourceParam(listResourceParam);
    std::string resourceType = listResourceParam.resourceExtendInfo.protocol;
    auto appManagerPtr = ProtectPluginFactory::GetInstance().Create(resourceType);
    if (appManagerPtr != nullptr) {
        ResourceResultByPage resourceResult;
        appManagerPtr->ListApplicationResource(resourceResult, listResourceParam);
        returnValue.swap(resourceResult.items);
        return;
    }
    HCP_Log(ERR, MODULE) << "List application resource failed,resourceType:"<< resourceType << HCPENDLOG;
}
bool ConstructResourceParam(const ListResourceRequest& request, ListResourceParam& listResourceParam)
{
    listResourceParam.applicationId = request.applications[0].id;
    listResourceParam.sharePath = request.applications[0].name;
    listResourceParam.pageNo = request.condition.pageNo;
    listResourceParam.pageSize = (request.condition.pageSize == 0) ?
                                  DEFAULT_PAGE_SIZE : request.condition.pageSize;
    listResourceParam.nasShareAuthInfo.authType = request.applications[0].auth.authType;
    listResourceParam.nasShareAuthInfo.authKey = request.applications[0].auth.authkey;
    listResourceParam.nasShareAuthInfo.authPwd = request.applications[0].auth.authPwd;
    if (!JsonHelper::JsonStringToStruct(request.applications[0].extendInfo, listResourceParam.resourceExtendInfo)) {
        HCP_Log(ERR, MODULE) << "JsonStringToStruct failed,application.extendInfo:"<<
            request.applications[0].extendInfo << HCPENDLOG;
        return false;
    }
    listResourceParam.path = listResourceParam.resourceExtendInfo.directory;
    if (listResourceParam.nasShareAuthInfo.authType == KRB5AUTHMODE) {
        if (!JsonHelper::JsonStringToStruct(request.applications[0].auth.extendInfo,
                                            listResourceParam.nasAuthExtentInfo)) {
            HCP_Log(ERR, MODULE) << "JsonStringToStruct failed."<< HCPENDLOG;
            return false;
        }
    }
    return true;
}
void ListApplicationResourceV2(ResourceResultByPage& returnValue, const ListResourceRequest& request)
{
    if (request.applications.empty()) {
        HCP_Log(ERR, MODULE) << "List application resource failed,applications is empty" << HCPENDLOG;
        return;
    }
    Json::Value appEnvStr;
    StructToJson(request.appEnv, appEnvStr);
    HCP_Log(INFO, MODULE) << "ListApplicationResource, parameter:" <<
        appEnvStr.toStyledString() << HCPENDLOG;
    Json::Value applicationStr;
    StructToJson(request.applications[0], applicationStr);
    HCP_Log(INFO, MODULE) << "ListApplicationResource, parameter:" <<
        applicationStr.toStyledString() << HCPENDLOG;
   
    ListResourceParam listResourceParam;
    if (!ConstructResourceParam(request, listResourceParam)) {
        return;
    }
    PrintListResourceParam(listResourceParam);
    std::string resourceType = listResourceParam.resourceExtendInfo.protocol;
    auto startTime = std::chrono::steady_clock::now();
    auto appManagerPtr = ProtectPluginFactory::GetInstance().Create(resourceType);
    if (appManagerPtr != nullptr) {
        appManagerPtr->ListApplicationResource(returnValue, listResourceParam);
        auto endTime = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
        HCP_Log(DEBUG, MODULE) << "ListApplicationResource, cost=" << duration.count() <<"ms" << HCPENDLOG;
        return;
    }
    HCP_Log(ERR, MODULE) << "List application resource failed,resourceType:"<< resourceType << HCPENDLOG;
    AppProtect::AppProtectPluginException appException;
    appException.__set_code(E_RESOURCE_TYPE_INVALID);
    appException.__set_message("invalid resourceType");
    throw appException;
}

void PrintListResourceParam(const ListResourceParam& listResourceParam)
{
    HCP_Log(INFO, MODULE) << "PrintListResourceParam, parameter:" <<
        "applicationId:"<<listResourceParam.applicationId <<
        ",sharePath:"<< listResourceParam.sharePath <<
        ",path:"<< listResourceParam.path <<
        ",fileType:"<< listResourceParam.resourceExtendInfo.fileType <<
        ",protocol:"<< listResourceParam.resourceExtendInfo.protocol <<
        ",authType:"<< listResourceParam.nasShareAuthInfo.authType <<
        ",pageNo:"<< listResourceParam.pageNo <<
        ",pageSize:"<< listResourceParam.pageSize << HCPENDLOG;
}

void TransformParm(ListResourceParam& returnResourceParam,
                   const ListApplicationShareInfo& shareInfo,
                   const Application& application,
                   const ApplicationEnvironment& appEnv)
{
    returnResourceParam.applicationId = application.id;
    returnResourceParam.sharePath = application.name;
    if (shareInfo.shareInfoReq.targetType == "HOMOGENEOUS") {
        returnResourceParam.nasShareAuthInfo.authKey = shareInfo.shareInfoReq.cifsAuth.username;
        returnResourceParam.nasShareAuthInfo.authPwd = shareInfo.shareInfoReq.cifsAuth.password;
        returnResourceParam.sharePath = shareInfo.shareInfoReq.name;
        returnResourceParam.resourceExtendInfo.serviceIp = shareInfo.shareInfoReq.shareIp;
    } else if (shareInfo.shareInfoReq.targetType == "HETEROGENEOUS") {
        NasShareExtendInfo shareExtendInfo {};
        if (!JsonHelper::JsonStringToStruct(application.extendInfo, shareExtendInfo)) {
            HCP_Log(ERR, MODULE) << "NasShareExtendInfo is invaild." << HCPENDLOG;
            return;
        }
        returnResourceParam.nasShareAuthInfo.authKey = application.auth.authkey;
        returnResourceParam.nasShareAuthInfo.authPwd = application.auth.authPwd;
        returnResourceParam.resourceExtendInfo.serviceIp = shareExtendInfo.ip;
    }
    returnResourceParam.path = shareInfo.path;
    returnResourceParam.pageNo = shareInfo.pageNo;
    returnResourceParam.pageSize = shareInfo.pageSize;
    returnResourceParam.resourceExtendInfo.protocol = shareInfo.shareInfoReq.protocol;
    returnResourceParam.resourceExtendInfo.fileType= NATIVE_FILE_TYPE;
    returnResourceParam.nasShareAuthInfo.authType = shareInfo.shareInfoReq.cifsAuth.mode;
     // kerborse
    if (shareInfo.shareInfoReq.cifsAuth.mode == KRB5AUTHMODE) {
        NasAuthExtentInfo authExtendInfo;
        if (!JsonHelper::JsonStringToStruct(appEnv.auth.extendInfo, authExtendInfo)) {
            HCP_Log(ERR, MODULE) << "NasAuthExtentInfo is invaild." << HCPENDLOG;
            return;
        }
        returnResourceParam.nasShareAuthInfo.authKey = appEnv.auth.authkey;
        returnResourceParam.nasShareAuthInfo.authPwd = authExtendInfo.password;
        returnResourceParam.nasAuthExtentInfo.keytab = authExtendInfo.keytab;
        returnResourceParam.nasAuthExtentInfo.krb5Conf = authExtendInfo.krb5Conf;
    }
    return;
}

int CheckSmbConnection(const CifsConnectParams& params)
{
    std::vector<SmbVersion> cifsVersion {SmbVersion::VERSION0302, SmbVersion::VERSION0300,
        SmbVersion::VERSION0210, SmbVersion::VERSION0202};
    SmbAuthType smbRmtAuthType;
    if (params.authType == "krb5") {
        smbRmtAuthType = SmbAuthType::KRB5;
    } else if (params.authType == "ntlmssp") {
        smbRmtAuthType = SmbAuthType::NTLMSSP;
    } else {
        HCP_Log(INFO, MODULE) << "Wrong authType for cifs share." << HCPENDLOG;
        return MP_FAILED;
    }
    
    bool smbEncryption = (params.encryption == SMB_ENCRYPTION) ? true : false;
    std::string agentHomePath = Module::EnvVarManager::GetInstance()->GetAgentHomePath();
    std::string krb5CcnameFile = agentHomePath + KRB5CCNAMEPREFIX + params.applicationId;
    std::string krb5ConfigFile = agentHomePath + KRB5CONFIGPREFIX + params.applicationId + KRB5CONFIGPOSTFIX;

    for (auto version : cifsVersion) {
        for (auto ip : params.nasServerIpList) {
            SmbContextArgs rmtPars = {
                params.domainName, ip, params.shareName, params.username,
                params.password, krb5CcnameFile, krb5ConfigFile,
                smbEncryption, false, 60, smbRmtAuthType, version};

            SmbContextWrapper rmtSmb(rmtPars);
            if (!rmtSmb.Init()) {
                continue;
            }

            if (rmtSmb.SmbConnect()) {
                return MP_SUCCESS;
            }
        }
    }
    return MP_FAILED;
}

}

